/**
 * 
 */
package jspeech;

import jXMLReader.XMLReader;

import java.io.IOException;

import jcommand.CommandSet;
import jspeech.exception.*;
import jspeech.sys.LinkLayer;

/**
 * Provides access to the speech recognition service which recognition in
 * background and developers design all commands before the main loop.
 * <p>
 * &nbsp;
 * <p>
 * <b>Workflow:</b>
 * <p>
 * <table>
 * <tr>
 * <td>Step 1:</td>
 * <td>Create <code>SpeechRecognizerListener</code></td>
 * </tr>
 * <tr>
 * <td>Step 2:</td>
 * <td>Set the recognizer commands by using <code>setCommand(String[])</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;Exception:</td>
 * <td><code>throw new SDKConnectionException</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;</td>
 * <td><code>throw new SDKNotFoundException</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;</td>
 * <td><code>throw new TimeoutException</code></td>
 * </tr>
 * <tr>
 * <td>Step 3:</td>
 * <td>Start the recognizer by using <code>start()</code></td>
 * </tr>
 * <tr>
 * <td>Step 4:</td>
 * <td>Use <code>recognizedCommand()</code> to get the recognized string</td>
 * </tr>
 * <tr>
 * <td>Step 5:</td>
 * <td>Close the recognizer by using <code>close()</code></td>
 * </tr>
 * </table>
 * <p>
 * &nbsp;
 * <p>
 * <b>Workflow when running <code>start()</code>:</b>
 * <p>
 * <table>
 * <tr>
 * <td>Step 1:</td>
 * <td>Create <code>LinkLayer</code> object. </td>
 * </tr>
 * <tr>
 * <td>Step 2:</td>
 * <td>Start establish the connection by using <code>LinkLayer.start()</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;Exception:</td>
 * <td><code>throw new SDKConnectionException</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;</td>
 * <td><code>throw new SDKNotFoundException</code></td>
 * </tr>
 * <tr>
 * <td>&nbsp;</td>
 * <td><code>throw new TimeoutException</code></td>
 * </tr>
 * <tr>
 * <td>Step 3:</td>
 * <td>Init the command by sending <code>INIT:</code> to the engine</td>
 * </tr>
 * </table>
 * <p>
 * 
 * &nbsp;
 * 
 * @author Kenneth Lo
 */
public class SpeechRecognizerListener {

	/**
	 * Full path of the Speech Recognization Engine
	 */
	private String sdk_Path;

	/**
	 * The number of retry count for setup the connection to the Speech
	 * Recognization Engine
	 */
	private int count;

	/**
	 * Timeout period (in ms).
	 */
	private int timeout;

	/**
	 * If the debug mode is set to true, detail flow message will print out to
	 * the console
	 */
	private boolean debug_mode;

	/**
	 * If this set to true, the engine will load from the path set from the
	 * config file, otherwise, the engines should start manually.
	 */
	private boolean load_engine_from_config;

	/**
	 * The communication layer for sending and revicing data between the
	 * <i>JSpeech</i> and <i>Recognization Engine</i>
	 */
	private LinkLayer linkLayer;

	/**
	 * Command Set - Command that need to run
	 */
	private CommandSet commandSet;

	/**
	 * Create the SpeechRecognizer object, bound to the default port (56001).
	 * 
	 */
	public SpeechRecognizerListener(CommandSet commandSet, String xmlConfigPath) {
		this(56001, commandSet, xmlConfigPath);
	}

	/**
	 * Create the SpeechRecognizer object, bound to the specific port.
	 * 
	 * @param port
	 *            port the port number
	 */
	public SpeechRecognizerListener(int port, CommandSet commandSet,
			String xmlConfigPath) {
		this.initVariable(xmlConfigPath);
		this.commandSet = commandSet;
		linkLayer = new LinkLayer(port, true, this.load_engine_from_config,
				this.debug_mode);
	}

	/**
	 * Start the Speech Recognizer
	 * 
	 * @throws EngineConnectionException
	 *             if JSpeech fail to establish the connection with the engine
	 * @throws EngineNotFoundException
	 *             if the executable of the engine cannot be found
	 * @throws TimeoutException
	 *             if the engine have not send back the reply after the timeout
	 *             period. The timeout can be set in
	 *             {@link jspeech.config.SpeechRecognizerConfiguration} class
	 */
	public void start() throws EngineConnectionException,
			EngineNotFoundException, TimeoutException {
		linkLayer.start(this.sdk_Path, this.timeout, commandSet);

		for (int i = 0; i < this.count; i++) {
			try {
				if (linkLayer.readFromSocket().equalsIgnoreCase(
						"SPEECHRECOGNIZER:CONNECTED")) {
					if (this.debug_mode)
						System.out.println(getInitWords());
					linkLayer.writeToSocket("INIT:" + getInitWords());
					return;
				} else {
					sleep();
				}
			} catch (RuntimeException re) {
				sleep();
			}
		}
		throw new TimeoutException();
	}

	/**
	 * Close the speech recognizer
	 * 
	 * @throws IOException
	 *             if an I/O error occurs when closing speech recognizer
	 */
	public void close() throws IOException {
		linkLayer.close();
	}

	/**
	 * Generate the init string for the recongnization engine for setting which
	 * words need to recognizer <br>
	 * <code>WORDS:Close;Internet</code>
	 * 
	 * @return string with init words formatted
	 */
	private String getInitWords() {
		String initWords = "WORDS:";

		String[] keys = commandSet.getCommandKeys();
		for (int i = 0; i < keys.length - 1; i++)
			initWords += (keys[i] + ";");

		initWords += keys[keys.length - 1];

		return initWords;
	}

	/**
	 * Sleep for a period of time. The time can be set from
	 * <code>SpeechRecognizerConfiguration</code>
	 * 
	 */
	private void sleep() {
		try {
			Thread.sleep(this.timeout / this.count);
		} catch (InterruptedException e) {
		}
	}

	/**
	 * Init the configurature value from XML config file
	 * 
	 * @param xmlConfigPath
	 */
	private void initVariable(String xmlConfigPath)
			throws XMLConfigFileException {
		try {
			XMLReader Configuration = new XMLReader(xmlConfigPath);

			this.sdk_Path = (String) Configuration
					.getConfig("JSpeech.SpeechRecognizer.SDKPath");

			this.timeout = (Integer) Configuration
					.getConfig("JSpeech.CommonConfiguration.Timeout");

			this.load_engine_from_config = (Boolean) Configuration
					.getConfig("JSpeech.CommonConfiguration.LoadEngineFromConfig");

			this.debug_mode = (Boolean) Configuration
					.getConfig("JSpeech.CommonConfiguration.DebugMode");

			this.count = (Integer) Configuration
					.getConfig("JSpeech.SpeechRecognizer.Count");

		} catch (RuntimeException e) {
			throw new XMLConfigFileException(xmlConfigPath, e);
		}
	}
}
